package org.example.gateway.config;

import cn.hutool.core.map.MapUtil;
import org.example.common.core.constants.CacheConstants;
import org.example.common.core.constants.Constants;
import org.example.common.redis.utils.RedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.Map;

/**
 * 认证管理器自定义O
 */
@Component
public class JwtAuthenticationManager implements ReactiveAuthenticationManager {
    @Autowired
    private TokenStore tokenStore;


    @Override
    public Mono<Authentication> authenticate(Authentication authentication) {
        return Mono.justOrEmpty(authentication)
                .filter(x->x instanceof BearerTokenAuthenticationToken)
                .cast(BearerTokenAuthenticationToken.class)
                .map(BearerTokenAuthenticationToken::getToken)
                .flatMap(accessToken->{
                    OAuth2AccessToken auth2AccessToken= tokenStore.readAccessToken(accessToken);
                    if(ObjectUtils.isEmpty(auth2AccessToken)){
                        return Mono.error(new InvalidTokenException("令牌无效"));
                    }else if(auth2AccessToken.isExpired()){
                        return Mono.error(new InvalidTokenException("令牌已过期"));
                    }
                    OAuth2Authentication auth2Authentication= tokenStore.readAuthentication(accessToken);
                    if(ObjectUtils.isEmpty(auth2Authentication)){
                        return Mono.error(new InvalidTokenException("令牌已过期"));
                    }
                    //处理注销账号s
                    Map<String,Object> map= auth2AccessToken.getAdditionalInformation();
                    if(MapUtil.isEmpty(map)){
                        return Mono.error(new InvalidTokenException("令牌无效"));
                    }
                    String authToken= (String)map.get(Constants.UUID);
                    String tenant_id=(String) map.get(Constants.TENANT_ID);
                    String grant_user=(String) map.get(Constants.GRANT_USER);
                    String device_type=(String) map.get(Constants.DEVICE_TYPE);
                    String user_name=(String) map.get("user_name");
                    String token_black= RedisUtils.getCacheObject(String.format(CacheConstants.TOKEN_BLACK_KEY,tenant_id,authToken));
                    //黑名单处理
                    if(!ObjectUtils.isEmpty(token_black)){
                        return Mono.error(new InvalidTokenException("令牌已过期"));
                    }
                    //单一用户登录处理
                    String redisToken=RedisUtils.getCacheObject(String.format(CacheConstants.LOGIN_ACCOUNT_KEY,tenant_id,
                            grant_user,device_type,user_name));
                    if(!authToken.equals(redisToken)){
                        //将该token加入黑名单
                        RedisUtils.setCacheObject(String.format(CacheConstants.TOKEN_BLACK_KEY,tenant_id,authToken),
                                authToken, Duration.ofMillis(auth2AccessToken.getExpiresIn()));
                        RedisUtils.setCacheObject(String.format(CacheConstants.LOGIN_TOKEN_KEY,tenant_id,grant_user,device_type,user_name),
                                authToken, Duration.ofMillis(auth2AccessToken.getExpiresIn()));
                        return Mono.error(new InvalidTokenException("令牌已过期"));
                    }
                    return Mono.just(auth2Authentication);
                })
                .cast(Authentication.class);
    }
}
